SSM Run CommandとEventBridge Schedulerでマウントポイントを監視する
こんにちは、なおにしです。
SSM Run CommandとEventBridge Schedulerを使用して、Mountpoint for Amazon S3のマウントポイントを監視してみたのでご紹介します。
はじめに
EC2 インスタンス上でNFSなどを使用してネットワークストレージをマウントすることはよくあるかと思います。
EFSやFSx for NetApp ONTAPなどストレージ側の可用性は非常に高くても、マウントする側のEC2で誤操作など何らかの要因でマウントが解除されたり、正常にマウントできていない状態になる可能性もあります。
特に、Mountpoint for Amazon S3のようなFUSEベースのクライアントである場合、メモリ逼迫によるOOM killerなど、クライアントソフトとして異常が発生する可能性はより高くなるかと思います。
そういった状況に備える場合は、正常にマウントできていることを監視し、必要に応じてアラート通知する必要があります。
例えばオンプレミスのサーバ単体であれば、シェルスクリプトとcronを使用して一定間隔でマウント状況を確認し、異常を検知したらメールで通知するといった機能を作り込んでいることもあるかと思います。
一方、AWS上で複数台のEC2においてマウント状況を監視する場合は、もちろん同様の方法で監視を実現することも可能ですが、表題のとおりの方法でサーバ毎のスクリプト配置やcron設定をせずに実装することもできますので、実際に試してみました。
やってみた
前提
3台のEC2(AL2023)で特定のS3バケットをMountpoint for Amazon S3を用いてマウントします。Run Commandの動作を確認するために、EC2のうち1台はマウントしていない状態から開始します。マウントポイントのディレクトリは[/mnt]で共通とします。
Mountpoint for Amazon S3については以下の記事をご参照ください。
マウント状態の確認方法
Linuxでは対象ディレクトリがマウントポイントであるかどうかを確認するための「mountpoint」コマンドが提供されています。AL2023でも当該コマンドはプリインストールされています。
「mountpoint」コマンドでは、対象がマウントポイントであるかどうかに応じて以下の終了コードを返します。
- 終了コード0:対象がマウントポイントの場合
- 終了コード1:パーミッションやシステムエラーによってコマンドが失敗した場合
- 終了コード32:対象がマウントポイントでない場合
実際にMountpoint for Amazon S3と組み合わせて確認した結果は以下のとおりです。
### マウント前
# mountpoint /mnt
/mnt is not a mountpoint
# echo $?
32
### マウント実行
# mount-s3 naonishi-dev-s3bucket-ec-site-contents /mnt --allow-other --allow-delete
bucket naonishi-dev-s3bucket-ec-site-contents is mounted at /mnt
# mountpoint /mnt
/mnt is a mountpoint
# echo $?
0
### アンマウント
# umount /mnt
# mountpoint /mnt
/mnt is not a mountpoint
# echo $?
32
特にMountpoint for Amazon S3の場合は、例えばマウント中にEC2からインスタンスプロファイルからS3へのアクセス許可ポリシーを削除した場合、マウントが解除されるのではなく以下のようにエラーが出力されます。
# mount-s3 naonishi-dev-s3bucket-ec-site-contents /mnt --allow-other --allow-delete
bucket naonishi-dev-s3bucket-ec-site-contents is mounted at /mnt
# mountpoint /mnt
/mnt is a mountpoint
# echo $?
0
# ls /mnt/
test.txt
# echo $?
0
### インスタンスプロファイルからS3へのアクセス許可ポリシーを削除
# mountpoint /mnt
/mnt is a mountpoint
# echo $?
0
# ls /mnt/
ls: reading directory '/mnt/': Input/output error
# echo $?
2
S3へのアクセス許可ポリシーが削除された場合に限らず、上記のような状態になることもあるかもしれません。そういった場合には「mountpoint」コマンドだけでは監視として不十分のため、上記のように実際にマウントポイント内のファイルが参照できるのかといった確認と併せて監視する必要があります。
このようにそれぞれの処理の終了コードの値に応じて最終的な成功/失敗を判定する必要があるパターンについては、以下のとおりドキュメントにも記載があります。
上記ドキュメントの内容を参考に、今回はシンプルに以下のコマンドでマウントポイントの監視を試してみます。
mountpoint /mnt
if [ $? != 0 ]; then
echo "Failed"
exit 1
fi
ls /mnt
SSM Run Commandによる監視コマンドの実行
まずは1度マウント状態を確認してみるために、Run Commandを設定します。
今回はCloudFormationで環境を作成していますので、インスタンス共通のタグをターゲットに指定します。また、タイムアウトの設定箇所が2箇所ありますが、こちらについては以下のドキュメントをご参照ください。
今回はAmazon SNS による通知だけを試すので、デフォルトで有効になっている「コマンド出力の Amazon S3バケットへの書き込み」は無効化します。SNSの通知に使用するIAMロールの作成方法については以下のドキュメントをご参照ください。
今回作成したIAMロールに割り当てたポリシーは以下のとおりです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"Resource": "arn:aws:sns:ap-northeast-1:(AWSアカウントID):Alarms_Topic"
}
]
}
通知条件はRun Commandで「失敗」した場合を指定します。以下2つの選択肢もありますが、一旦はデフォルトの「コマンドのステータスが変更されたときのコマンド概要」で実行してみます。
- コマンドのステータスが変更されたときのコマンド概要
- 各インスタンスのコマンドステータスが変更されたときのインスタンスベースの通知
また、[AWS コマンドラインインターフェイスのコマンド]には上記までの設定をAWS CLI で実行する場合のコマンドが自動生成されます。こちらは後ほどEventBridge Schedulerで設定する時に参考にするためにコピーして控えておきます。
実行した結果は以下のとおりです。
EC2インスタンス3台のうち1台はS3バケットをマウントしていないため、上記の結果は想定どおりです。SNSによって通知されたメールは以下のようになっています。
Run Commandの実行が失敗したことは分かりますが、どのインスタンスで発生したのかが分かる内容にはなっていません。このため、改めて以下のとおりSNS通知の条件を変更して再実行してみます。
次に受信したメールは以下のとおりでした。対象のインスタンスIDがメール本文に含まれていることが分かります。
試しにS3バケットをマウント済みの1台のEC2インスタンスでアンマウントしてRun Commandを再実行したところ、メールは2件立て続けに届きました。
上記のような挙動となりますので、監視対象インスタンスの台数や運用方針に基づいてSNS通知の条件は選択していただければと思います。
ちなみに、先ほどアンマウントしたEC2インスタンスで再度S3バケットをマウントし、元々マウントしていなかったEC2は停止した状態で再度Run Commandを実行した結果は以下のとおりです。
停止中のインスタンスについてはSSM Agent とも通信できないため、Run Commandの実行対象とならず、実行結果の全体的なステータスとしては「成功」になりました。
EventBridge Schedulerの設定準備
設定を始める前に、EventBridge Schedulerで実行するSSMの「SendCommand」APIに指定するリクエストパラメータを作成します。APIリファレンスは以下をご参照ください。
上記ドキュメントを読み解いてリクエスト用のJSONデータを作成すれば良いのですが、慣れていないとそれなりに時間がかかりそうです。
ここで、先ほど控えていたAWS CLIによるRun Commandの実行を利用します。以下のとおり、AWS CLIで実行するとレスポンスデータがJSON形式で返ってきます。
# aws ssm send-command --document-name "AWS-RunShellScript" --document-version "1" --targets '[{"Key":"tag:aws:cloudformation:stack-name","Values":["test-naonishi-infra"]}]' --parameters '{"commands":["mountpoint /mnt"," if [ $? != 0 ]"," then"," echo \"Failed\""," exit 1"," fi"," ls /mnt"],"workingDirectory":[""],"executionTimeout":["60"]}' --timeout-seconds 30 --max-concurrency "50" --max-errors "0" --service-role-arn "arn:aws:iam::(AWSアカウントID):role/test-naonishi-ssm-ssn-publish" --notification-config '{"NotificationArn":"arn:aws:sns:ap-northeast-1:(AWSアカウントID):Alarms_Topic","NotificationEvents":["Failed"],"NotificationType":"Command"}' --region ap-northeast-1
{
"Command": {
"CommandId": "dba7557f-43c0-4aea-8b90-91a6bb3356dd",
"DocumentName": "AWS-RunShellScript",
"DocumentVersion": "1",
"Comment": "",
"ExpiresAfter": "2024-12-23T19:37:25.946000+09:00",
"Parameters": {
"commands": [
"mountpoint /mnt",
" if [ $? != 0 ]",
" then",
" echo \"Failed\"",
" exit 1",
" fi",
" ls /mnt"
],
"executionTimeout": [
"60"
],
"workingDirectory": [
""
]
},
"InstanceIds": [],
"Targets": [
{
"Key": "tag:aws:cloudformation:stack-name",
"Values": [
"test-naonishi-infra"
]
}
],
"RequestedDateTime": "2024-12-23T19:35:55.946000+09:00",
"Status": "Pending",
"StatusDetails": "Pending",
"OutputS3Region": "ap-northeast-1",
"OutputS3BucketName": "",
"OutputS3KeyPrefix": "",
"MaxConcurrency": "50",
"MaxErrors": "0",
"TargetCount": 0,
"CompletedCount": 0,
"ErrorCount": 0,
"DeliveryTimedOutCount": 0,
"ServiceRole": "arn:aws:iam::(AWSアカウントID):role/test-naonishi-ssm-ssn-publish",
"NotificationConfig": {
"NotificationArn": "arn:aws:sns:ap-northeast-1:(AWSアカウントID):Alarms_Topic",
"NotificationEvents": [
"Failed"
],
"NotificationType": "Command"
},
"CloudWatchOutputConfig": {
"CloudWatchLogGroupName": "",
"CloudWatchOutputEnabled": false
},
"TimeoutSeconds": 30,
"AlarmConfiguration": {
"IgnorePollAlarmFailure": false,
"Alarms": []
},
"TriggeredAlarms": []
}
}
また、リクエスト用の雛形のJSONデータは、以下のようにして取得できます。
# aws ssm send-command --generate-cli-skeleton
{
"InstanceIds": [
""
],
"Targets": [
{
"Key": "",
"Values": [
""
]
}
],
"DocumentName": "",
"DocumentVersion": "",
"DocumentHash": "",
"DocumentHashType": "Sha256",
"TimeoutSeconds": 0,
"Comment": "",
"Parameters": {
"KeyName": [
""
]
},
"OutputS3Region": "",
"OutputS3BucketName": "",
"OutputS3KeyPrefix": "",
"MaxConcurrency": "",
"MaxErrors": "",
"ServiceRoleArn": "",
"NotificationConfig": {
"NotificationArn": "",
"NotificationEvents": [
"All"
],
"NotificationType": "Command"
},
"CloudWatchOutputConfig": {
"CloudWatchLogGroupName": "",
"CloudWatchOutputEnabled": true
},
"AlarmConfiguration": {
"IgnorePollAlarmFailure": true,
"Alarms": [
{
"Name": ""
}
]
}
}
上記のリクエスト用雛形とレスポンスデータを比較することで、多少はリクエスト用のJSONデータの作成がやりやすくなるかと思います。また、生成AIを使用してレスポンスデータからリクエストデータを作成してもらうと効率的かもしれません。
今回はリクエスト用のJSONデータとして以下を使用します。指定がなかったパラメータについては削除しました。
{
"Targets": [
{
"Key": "tag:aws:cloudformation:stack-name",
"Values": [
"test-naonishi-infra"
]
}
],
"DocumentName": "AWS-RunShellScript",
"DocumentVersion": "1",
"TimeoutSeconds": 30,
"Parameters": {
"commands": [
"mountpoint /mnt",
" if [ $? != 0 ]",
" then",
" echo \"Failed\"",
" exit 1",
" fi",
" ls /mnt"
],
"executionTimeout": [
"60"
]
},
"MaxConcurrency": "50",
"MaxErrors": "0",
"ServiceRoleArn": "arn:aws:iam::(AWSアカウントID):role/test-naonishi-ssm-ssn-publish",
"NotificationConfig": {
"NotificationArn": "arn:aws:sns:ap-northeast-1:(AWSアカウントID):Alarms_Topic",
"NotificationEvents": [
"Failed"
],
"NotificationType": "Command"
}
}
上記のJSONデータによるSendCommand がRun Commandで確認した挙動と同じになるかどうかテストするには、以下のようにAWS CLIから実行します。
# aws ssm send-command --cli-input-json file://request.json
また、EventBridge SchedulerでSendCommand を実行するためのIAMロールも作成しておきます。信頼されたエンティティについてはドキュメントを参考に以下を設定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "scheduler.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
設定するIAMポリシーについては今回は以下を指定しました。ssm:SendCommandについては実運用する際は対象リソースをより細かく制限した方が良いかと思います。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:SendCommand",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::(AWSアカウントID):role/test-naonishi-ssm-ssn-publish"
}
]
}
注意する箇所としては、Amazon SNS で通知するための許可ポリシーとして先ほどSendCommand 内で指定した(= Run Commandを実行する際に使用した)既存のIAMロールに対してiam:PassRoleを許可している点です。PassRoleといえば以下の記事ですので、イメージしづらい場合はぜひご参照ください。
EventBridge Schedulerの設定
それではEventBridge Schedulerを設定していきます。
1分毎に監視を実行するため、以下のように指定します。
フレックスタイムウィンドウや夏時間に関する設定はせずに次に進みます。
ターゲットとしてSystems Manager → SendCommandを指定し、先ほど準備したJSONデータを貼り付けます。
事前に作成したロールを指定します。
最後に設定内容を確認して問題なければ[スケジュールを作成]で完了します。
完了後、Run Commandのコマンド履歴から1分おきにコマンドが実行されていることが確認できました。ステータスが失敗になっていますので、1分おきにメール通知もきています。
まとめ
今回はMountpoint for Amazon S3にフォーカスして監視を試してみましたが、Mountpoint for Amazon S3の用途としてはドキュメントにも以下の記載があり、基本的には読み取りのワークロードを対象としています。
Mountpoint for Amazon S3は、データレイク、機械学習トレーニング、画像レンダリング、自動運転車シミュレーション、抽出、変換、ロード (ETL) など、本番環境での大規模な読み取り負荷の高いアプリケーション用に一般提供されています。
とはいえ、オプションを指定することで書き込み操作も可能となり、便利であるためEFSのように使用したくなるかもしれませんが、ドキュメントにも以下の記載があります。
Mountpoint does not implement all the features of a POSIX file system and this may affect compatibility with your application.
このため、ファイルのリネームなど一般的なファイルシステムのマウントであれば可能な操作ができなかったりするので、実際に使用する場合はアプリケーションとの組み合わせなど十分に検証する必要があることにご注意ください。
本記事がどなたかのお役に立てれば幸いです。